//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: table.c 757 2007-05-10 08:19:31Z roger $
//

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <unihan_im.h>
#include <unihan_im_keycodes.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#include <SWTable.h>

#include "im_config.h"
#include "candidates.h"
#include "context.h"
#include "table.h"
#include "const.h"
#include "table_io.h"

static int table_init(UnihanIM *uim);
static int table_done(UnihanIM *uim);
static int table_desktop_init(TableDesktopList *desktop);
static void table_desktop_done(TableDesktopList *desktop);
static int confirm_user_data(TableDesktopList *desktop);

static int table_create_session(UnihanIM *uim);
static int table_destroy_session(UnihanIM *uim);
static int table_session_init(TableSession *session);
static void table_session_done(TableSession *session);

static int table_handle_event(UnihanIM *uim, IMEvent *ev);
static void table_get_im_list_for_path(IMInfo **im_list, const char *path);
static int table_read_tabfile_header(IMInfo *im, char *fullpath);

static TableDesktopList *desktop_list = NULL;
//static TableSessionList *session_list = NULL;

IMInfo *
table_get_im_list(void)
{
	IMInfo *im = NULL;

	table_get_im_list_for_path(&im, "/usr/lib/unihan2/im_modules/SWTable");
	return im;
}

void
table_get_im_list_for_path(IMInfo **im_list, const char *path)
{
	DIR *dir;
#if 0
	UnihanPropertyArgList new_property;
#endif

	union {
		struct dirent d;
		char b[sizeof(struct dirent) + NAME_MAX + 1];
	} u;

	struct dirent *res = NULL;
	IMMethods *imm = NULL;
	IMInfo *im = NULL;

	dir = opendir(path);
	if (!dir) {
		// FIXME: error handling
		return;
	}

	while (readdir_r(dir, &u.d, &res) == 0 && res != NULL) {
		char *ext = strrchr(res->d_name, (int)'.');
		char *fullpath = NULL;
		if (ext && !strcmp(FILE_SUFFIX_TAB, ext)) {
#if DEBUG
			fprintf(stderr, "table : find IM : %s\n", res->d_name);
#endif
			fullpath = (char*)malloc
					(strlen(path)+strlen(res->d_name)+2);
			sprintf(fullpath, "%s/%s", path, res->d_name);
		} else
			continue;

		im = (IMInfo *)malloc(sizeof(IMInfo));
		memset(im, 0, sizeof(IMInfo));
		if (!table_read_tabfile_header(im, fullpath)) {
			free(im);
			continue;
		}

		imm = (IMMethods *)malloc(sizeof(IMMethods));
		memset(imm, 0, sizeof(IMMethods));
		imm->init = table_init;
		imm->done = table_done;
		imm->create_session = table_create_session;
		imm->destroy_session = table_destroy_session;
		imm->handle_event = table_handle_event;

		im->id = strdup(res->d_name);
		*(im->id + (ext - res->d_name)) = 0;
		im->version = strdup("1.0");
		im->methodTable = imm;
		im->imUserData = fullpath;

#if 0
		new_property = (UnihanPropertyArgList)malloc(sizeof(UnihanPropertyArgs));
		memset(new_property, 0, sizeof(UnihanPropertyArgs));
		new_property->type = UNIHAN_PROPERTY_NONE;
			
		im->proplist = new_property;
#endif
		im->next = *im_list;
		*im_list = im;
	}

	closedir(dir);
}

static int
table_read_tabfile_header(IMInfo *im, char *fullpath)
{
	FILE *tabfile = NULL;
	size_t len;
	char tmp20[20];
	TableHeader header;

	tabfile = fopen(fullpath, "r");
	if (!tabfile)
		return FALSE;

	len = strlen(IM_TBL_SIG)+1;
	if (fread(tmp20, len, 1, tabfile) != 1) {
		fclose(tabfile);
		return FALSE;
	}
	if (strncmp(IM_TBL_SIG, tmp20, len-1) != 0) {
		fclose(tabfile);
		return FALSE;
	}

	/* get table header */
	if (fread(&header, sizeof(TableHeader), 1, tabfile) != 1) {
		fclose(tabfile);
		return FALSE;
	}
	im->name = strdup(header.cname);

	fclose(tabfile);
	return TRUE;
}

static int
table_init(UnihanIM *uim)
{
	TableDesktopList *desktop = desktop_list;
	int exist = 0;
	char *host_name = uim->host_name;
	char *user_name = uim->user_name;

#if DEBUG
	fprintf(stderr, "table %s@%s: table_init() for %s\n",
			user_name, host_name, uim->id);
#endif

	/* check desktop list */
	while (desktop) {
		if (strcmp(desktop->hostName, host_name) &&
				strcmp(desktop->userName, user_name)) {
			exist = 1;
			break;
		}
		desktop = desktop->next;
	}

	if (!exist) {
		desktop = (TableDesktopList *)malloc(sizeof(TableDesktopList));
		if (!desktop)
			return FALSE;
		memset(desktop, 0, sizeof(TableDesktopList));
		desktop->next = desktop_list;
		desktop->hostName = host_name;
		desktop->userName = user_name;
		desktop->tableUserData = uim->imUserData;
		if (desktop_list)
			desktop_list->prev = desktop;
		desktop_list = desktop;
		if (!table_desktop_init(desktop)) {
			table_desktop_done(desktop);
			free(desktop);
			return FALSE;
		}
	}

	uim->desktopUserData = desktop;
	return TRUE;
}

static int
table_done(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "table: table_done() for %s\n", uim->id);
#endif
	if (uim->desktopUserData) {
		TableDesktopList *desktop =
				(TableDesktopList *)uim->desktopUserData;
		TableDesktopList *prev = desktop->prev;
		if (prev)
			prev->next = desktop->next;
		if (desktop->next)
			desktop->next->prev = prev;
		table_desktop_done(desktop);
		free(desktop);
		uim->desktopUserData = NULL;
	}
	return TRUE;
}

static int
table_desktop_init(TableDesktopList *desktop)
{
#if DEBUG
	fprintf(stderr, "table : table_desktop_init()\n");
#endif
	char dir[256];

	memset(dir, 0, 256);
	sprintf(dir, "%s/%s@%s", TABLE_DATA_DIR,
			desktop->userName,
			desktop->hostName);
	if (access(TABLE_DATA_DIR, F_OK) != 0)
		mkdir(TABLE_DATA_DIR, 0700);
	if (access(dir, F_OK) != 0)
		mkdir(dir, 0700);

	confirm_user_data(desktop);
	return TRUE;
}

static void
table_desktop_done(TableDesktopList *desktop)
{
#if DEBUG
	fprintf(stderr, "table : table desktop done\n");
#endif
	PhraseTableList *ptlist;
	if (desktop->baseTable) {
		munmap(desktop->baseTable->map, desktop->baseTable->size);
		free(desktop->baseTable);
	}

	ptlist = desktop->phraseTableList;
	while(ptlist) {
		desktop->phraseTableList = ptlist->next;
		munmap(ptlist->map, ptlist->size);
		free(ptlist);
		ptlist = desktop->phraseTableList;
	}
}

static int
confirm_user_data(TableDesktopList *desktop)
{
	BaseTable *base_table;
	Adjuster *adjuster;
	PhraseTableList *ptlist;
	char *orig_path = strdup((char *)desktop->tableUserData);
	char *orig_dir = NULL;
	char *dest_dir = TABLE_DATA_DIR;
	int tabfilefd, adjfilefd;
	FILE *tabfile, *adjfile;
	struct stat tabfilestat, adjfilestat;
	long offset = 0;
	DIR *dir;
	union {
		struct dirent d;
		char b[sizeof(struct dirent) + NAME_MAX + 1];
	} u;
	struct dirent *res = NULL;
	char *table_ename = get_table_ename((const char *)orig_path);
#if DEBUG
	fprintf(stderr, "confirming user data %s@%s\n",
			desktop->userName, desktop->hostName);
#endif

/*********************************************************************/
	desktop->baseTable = base_table =
			(BaseTable *)malloc(sizeof(BaseTable));
	memset(base_table, 0, sizeof(BaseTable));
	tabfilefd = open(orig_path, O_RDONLY, S_IRUSR);
	if (!tabfilefd == -1) {
		free(base_table);
		return FALSE;
	}
	
	tabfile = fdopen(tabfilefd, "r");
	if (!tabfile) {
		free(base_table);
		close(tabfilefd);
		return FALSE;
	}

	fstat(tabfilefd, &tabfilestat);
	base_table->size = tabfilestat.st_size;
#if DEBUG
	fprintf(stderr, "table : base table size : %d\n", base_table->size);
#endif
	base_table->map = mmap(NULL,
			base_table->size,
			PROT_READ,
			MAP_SHARED,
			tabfilefd,
			0);

	if (base_table->map == MAP_FAILED) {
		free(base_table);
		fclose(tabfile);
		close(tabfilefd);
		return FALSE;
	}

	base_table->tableSig = (char *)
			(base_table->map + offset);
#if DEBUG
	fprintf(stderr, "table : get table sig : %s\n", base_table->tableSig);
#endif
	offset += strlen(IM_TBL_SIG) + 1;
	base_table->header = (TableHeader *)
			(base_table->map + offset);
#if DEBUG
	fprintf(stderr, "table : get table header ename : %s\tcname : %s\n \
			hints : %d\n \
			numKey : %d\tnumChar : %d\tkeyTableLen : %d\n	\
			charTableLen : %d\tnumRevChar : %d\n",
			base_table->header->ename,
			base_table->header->cname,
			base_table->header->hints,
			base_table->header->numKey,
			base_table->header->numChar,
			base_table->header->keyTableLen,
			base_table->header->charTableLen,
			base_table->header->numRevChar);
#endif
	offset += sizeof(TableHeader);
	base_table->keyName = (KeyNameTable *)
			(base_table->map + offset);
	offset += sizeof(KeyNameTable) * 0x60;
	base_table->fastIdx = (FastIdx *)
			(base_table->map + offset);
	offset += sizeof(FastIdx) * 0x60;
	base_table->checkListIndex = (CheckListIndex *)
			(base_table->map + offset);
	offset += base_table->header->numChar * sizeof(CheckListIndex);
	base_table->keyData = (char *)
			(base_table->map + offset);
	offset += base_table->header->keyTableLen;
	base_table->charData = (char *)
			(base_table->map + offset);
	offset += base_table->header->charTableLen;
	base_table->rsTable = (RevSearchTable *)
			(base_table->map + offset);
/****************************************************************/
	char dest_path[256];
	char adjfilename[256];
	char *pointer;

	memset(adjfilename, 0, 256);
	strcpy(adjfilename, orig_path);
	pointer = strchr(adjfilename, '.');
	*pointer = 0x0;
	strcat(adjfilename, FILE_SUFFIX_ADJ);
	while (*pointer != '/')
		pointer--;
	pointer++;

	sprintf(dest_path, "%s/%s@%s/%s",
			TABLE_DATA_DIR,
			desktop->userName,
			desktop->hostName,
			pointer);
	if (access(dest_path, F_OK) != 0)
		filecp(dest_path, adjfilename);

	desktop->adjuster = adjuster = (Adjuster *)malloc(sizeof(Adjuster));
	memset(adjuster, 0, sizeof(adjuster));
	adjfilefd = open(dest_path, O_RDWR, S_IRUSR|S_IWUSR);
	if (!adjfilefd == -1) {
		free(adjuster);
		desktop->adjuster = NULL;
	} else {
		adjfile = fdopen(adjfilefd, "r");
		if (!adjfile) {
			free(adjuster);
			desktop->adjuster = NULL;
			close(adjfilefd);
		} else {
			fstat(adjfilefd, &adjfilestat);
			adjuster->size = adjfilestat.st_size;
			adjuster->map = mmap(NULL,
					adjuster->size,
					PROT_READ|PROT_WRITE,
					MAP_SHARED,
					adjfilefd,
					0);

			if (adjuster->map == MAP_FAILED) {
				free(adjuster);
				desktop->adjuster = NULL;
				fclose(tabfile);
				close(tabfilefd);
			} else {
				offset = 0;
				adjuster->numMaxFreq = *((int*)adjuster->map);
				offset += sizeof(int);
				adjuster->maxFreqTable = adjuster->map;
				offset += adjuster->numMaxFreq*sizeof(MaxFrequence);
				adjuster->freqTable = adjuster->map + offset;
				offset += base_table->header->numChar *
						sizeof(Frequence);
				adjuster->stickTable = adjuster->map + offset;
			}
		}
	}
/****************************************************************/
	orig_dir = get_table_dir(orig_path);
	if (!orig_dir)
		return;

	dir = opendir(orig_dir);
	if (!dir) {
		// FIXME: error handling
		return;
	}

	while (readdir_r(dir, &u.d, &res) == 0 && res != NULL) {
		char *ext = strrchr(res->d_name, (int)'.');
		char *ename;
		char fullpath[256];
		PhraseTableList *ptlist;
		int ptfilefd;
		FILE *ptfile;
		struct stat ptfilestat;
		if (ext && ( !strcmp(FILE_SUFFIX_BIN, ext) || !strcmp(FILE_SUFFIX_UPB, ext))) {
			sprintf(fullpath, "%s/%s", orig_dir, res->d_name);
			ename = get_bin_ename(fullpath);
			if (strcmp(ename, table_ename) == 0) {
				ptlist = (PhraseTableList *)malloc(sizeof(PhraseTableList));
				memset(ptlist, 0, sizeof(ptlist));
				ptfilefd = open(fullpath, O_RDONLY, S_IRUSR);
				if (!ptfilefd == -1) {
					free(ptlist);
					continue;
				}

				ptfile = fdopen(ptfilefd, "r");
				if (!ptfile) {
					free(ptlist);
					close(ptfilefd);
					continue;
				}
				fstat(ptfilefd, &ptfilestat);
				ptlist->size = ptfilestat.st_size;
				ptlist->map = mmap(NULL,
						ptlist->size,
						PROT_READ,
						MAP_SHARED,
						ptfilefd,
						0);
				if (ptlist->map == MAP_FAILED) {
					free(ptlist);
					fclose(ptfile);
					close(ptfilefd);
					continue;
				}
				offset = 0;
				ptlist->tableSig = (char *)ptlist->map;
				offset += strlen(IM_TBL_SIG) + 1;
				ptlist->header = (BinHeader *)(ptlist->map + offset);
				offset += sizeof(BinHeader);
				ptlist->fastIdx = (FastIdx *)(ptlist->map + offset);
				offset += sizeof(FastIdx) * 0x60;
				ptlist->simCheckListIndex = (SimCheckListIndex *)(ptlist->map + offset);
				offset += sizeof(SimCheckListIndex) *
						ptlist->header->numCharList;
				ptlist->keyData = (char *)(ptlist->map + offset);
				offset += ptlist->header->keyTableLen;
				ptlist->charData = (char *)(ptlist->map + offset);
				/*if (strcmp(FILE_SUFFIX_BIN, ext)) {
					desktop->common_table = ptlist;
				} else {
					ptlist->next = desktop->user_table_list;
					desktop->user_table_list = ptlist;
				}*/
				desktop->numPhraseTable++;
				ptlist->next = desktop->phraseTableList;
				desktop->phraseTableList = ptlist;
			}
			free(ename);
		}
	}
	//FIXME : sort PhraseTableList

	free(table_ename);
	free(orig_path);
	free(orig_dir);
}

static int
table_create_session(UnihanIM *uim)
{
	TableSession *session = NULL;
	TableDesktopList *desktop = (TableDesktopList *)uim->desktopUserData;

#if DEBUG
	fprintf(stderr, "table: table_create_session() for %s\n", uim->id);
	if(!desktop)
		fprintf(stderr, "fatal error : desktop NULL\n");
#endif

	session = (TableSession *)malloc(sizeof(TableSession));
	if (!session)
		return FALSE;

	memset(session, 0, sizeof(TableSession));

	if (!table_session_init(session)) {
		table_session_done(session);
		free(session);
		return FALSE;
	}

	uim->sessionUserData = session;
	desktop->sessionCounter++;

	return TRUE;
}

static int
table_destroy_session(UnihanIM *uim)
{
	TableSession *session = (TableSession *)uim->sessionUserData;
	TableDesktopList *desktop = (TableDesktopList *)uim->desktopUserData;
#if DEBUG
	fprintf(stderr, "table: table_destroy_session() for %s\n", uim->id);
#endif

	if (session) {
		table_session_done(session);
		free(session);
		uim->sessionUserData = NULL;
	}

	desktop->sessionCounter--;

	return TRUE;
}

static int
table_session_init(TableSession *session)
{
#if DEBUG
	fprintf(stderr, "table : table session init\n");
#endif

	session->config = (TableConfig *)malloc(sizeof(TableConfig));
	memset(session->config, 0, sizeof(TableConfig));
	config_init(session->config);

	session->context = (Context *)malloc(sizeof(Context));
	memset(session->context, 0, sizeof(Context));
	context_init(session->context);
	return TRUE;
}

static void
table_session_done(TableSession *session)
{
#if DEBUG
	fprintf(stderr, "table : table session done\n");
#endif
	if (session->config)
		free(session->config);
	if (session->context) {
		context_done(session->context);
		free(session->context);
	}
}

static int
table_handle_event(UnihanIM *uim, IMEvent *ev)
{
	TableSession *session = (TableSession *)uim->sessionUserData;

#if DEBUG
	fprintf(stderr, "table: table_handle_event() for %s\n", uim->id);
#endif

	switch (ev->type) {
		case IM_KEY:
			return handle_key_event(uim, (IMKeyEvent *)ev);
		case IM_SWITCH:
			return handle_switch_event(uim, (IMSwitchEvent *)ev);
        case IM_FOCUS:
            return handle_focus_event(uim, (IMFocusEvent *)ev);
        case IM_CONFIG_CHANGE:
            return handle_config_change_event(uim,
						(IMConfigChangeEvent *)ev);
					
#if NEW_PROPERTY
		case IM_PROPERTY_TOGGLED:
			return handle_property_toggled_event(uim, (IMPropertyToggledEvent *)ev);
		case IM_PROPERTY_REGISTER:
			return handle_property_register_event(uim, (IMPropertyRegisterEvent *)ev);
#endif  

#if 0
		case IM_SYMBOL_USER_TABLE:
			return handle_symbol_user_table_event(uim,
					(IMUserTableEvent *)ev);
		case IM_WORD_USER_TABLE:
			return handle_word_user_table_event(uim,
					(IMUserTableEvent *)ev);
#endif
                case IM_NOTHING:
                        return TRUE;
	}
	return FALSE;
}

// vi:ts=4:nowrap:cin:expandtab
